﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Serialization;

namespace Mbs.Collections
{
    [Serializable]
    public class PropertySet : IPropertySet
    {
        /// <summary> Needed for support of late deserialization </summary>
        [Serializable]
        internal class SerializedValue
        {
            string content;

            public string Content
            {
                get { return content; }
            }

            public T Deserialize<T>()
            {
                XmlSerializer serializer = new XmlSerializer(typeof(T));
                return (T)serializer.Deserialize(new StringReader(content));
            }

            public SerializedValue(string content)
            {
                this.content = content;
            }
        }

        

        Dictionary<string,object> properties;

        public PropertySet()
        {
            properties = new Dictionary<string, object>();
        }

        public PropertySet(StringComparer comparer)
        {
            properties = new Dictionary<string, object>(comparer);
        }

        public PropertySet(IPropertySet properties)
        {
            this.properties = new Dictionary<string, object>();
            foreach (var item in properties)
                this.properties.Add(item.Key, item.Value);
        }

        public void Clear()
        {
            properties.Clear();
        }

        public void AddRange(IEnumerable<KeyValuePair<string, object>> items)
        {
            if (items != null && items.Count() > 0)
                foreach (var item in items)
                    this.properties.Add(item.Key, item.Value);
        }

        public object this[string property]
        {
            get
            {
                return Get(property);
            }
            set
            {
                Set(property, value);
            }
        }

        public string[] Keys
        {
            get
            {
                lock (properties)
                {
                    List<string> ret = new List<string>();
                    foreach (KeyValuePair<string, object> property in properties)
                        ret.Add(property.Key);
                    return ret.ToArray();
                }
            }
        }

        public object Get(string property)
        {
            lock (properties)
            {
                object val;
                properties.TryGetValue(property, out val);
                return val;
            }
        }

        public void Set(string propertyName, object value)
        {
            object oldValue = null;
            lock (properties)
            {
                if (!properties.ContainsKey(propertyName))
                {
                    properties.Add(propertyName, value);
                }
                else
                {
                    oldValue = Get(propertyName, value);
                    properties[propertyName] = value;
                }
            }
            OnPropertyChanged(new Mbs.ComponentModel.PropertyChangedEventArgs(propertyName, oldValue, value));
        }

        public bool Contains(string property)
        {
            lock (properties)
            {
                return properties.ContainsKey(property);
            }
        }

        public int Count
        {
            get
            {
                lock (properties)
                {
                    return properties.Count;
                }
            }
        }

        public bool Remove(string property)
        {
            lock (properties)
            {
                return properties.Remove(property);
            }
        }

        public override string ToString()
        {
            lock (properties)
            {
                StringBuilder sb = new StringBuilder();
                sb.Append("[Properties:{");
                foreach (KeyValuePair<string, object> entry in properties)
                {
                    sb.Append(entry.Key);
                    sb.Append("=");
                    sb.Append(entry.Value);
                    sb.Append(",");
                }
                sb.Append("}]");
                return sb.ToString();
            }
        }

       
        public T Get<T>(string property)
        {
            return Get<T>(property, default(T));
        }

        public T Get<T>(string property, T defaultValue)
        {
            lock (properties)
            {
                object o;
                if (!properties.TryGetValue(property, out o))
                {
                    properties.Add(property, defaultValue);
                    return defaultValue;
                }

                if (o is string)
                {
                    if (typeof(T) != typeof(string))
                    {
                        TypeConverter c = TypeDescriptor.GetConverter(typeof(T));
                        try
                        {
                            o = c.ConvertFromInvariantString(o.ToString());
                        }
                        catch (Exception ex)
                        {
                            var newEx = new Exception("Error loading property '" + property + "': " + ex.Message, ex); newEx.RaiseException();
                            o = defaultValue;
                        }
                        properties[property] = o; // store for future look up
                    }
                    else
                        o = StringParser.Parse(o as string);
                }
                else if (o is ArrayList && typeof(T).IsArray)
                {
                    ArrayList list = (ArrayList)o;
                    Type elementType = typeof(T).GetElementType();
                    Array arr = System.Array.CreateInstance(elementType, list.Count);
                    TypeConverter c = TypeDescriptor.GetConverter(elementType);
                    try
                    {
                        for (int i = 0; i < arr.Length; ++i)
                        {
                            if (list[i] != null)
                            {
                                arr.SetValue(c.ConvertFromInvariantString(list[i].ToString()), i);
                            }
                        }
                        o = arr;
                    }
                    catch (Exception ex)
                    {
                        var newEx = new Exception("Error loading property '" + property + "': " + ex.Message, ex);
                        newEx.RaiseException();
                        o = defaultValue;
                    }
                    properties[property] = o; // store for future look up
                }
                else if (!(o is string) && typeof(T) == typeof(string))
                {
                    TypeConverter c = TypeDescriptor.GetConverter(typeof(T));
                    if (c.CanConvertTo(typeof(string)))
                    {
                        o = c.ConvertToInvariantString(o);
                    }
                    else
                    {
                        o = o.ToString();
                    }
                }
                else if (o is SerializedValue)
                {
                    try
                    {
                        o = ((SerializedValue)o).Deserialize<T>();
                    }
                    catch (Exception ex)
                    {
                        var newEx = new Exception("Error loading property '" + property + "': " + ex.Message, ex);
                        o = defaultValue;
                    }
                    properties[property] = o; // store for future look up
                }
                try
                {
                    return (T)o;
                }
                catch (NullReferenceException)
                {
                    // can happen when configuration is invalid -> o is null and a value type is expected
                    return defaultValue;
                }
            }
        }

        protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, e);
            }
        }

        public event EventHandler<PropertyChangedEventArgs> PropertyChanged;

        #region IOriginator Members

        

        #endregion

        #region IEnumerable<KeyValuePair<string,object>> Members

        IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator()
        {
            return properties.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        IEnumerator IEnumerable.GetEnumerator()
        {
            return properties.GetEnumerator();
        }

        #endregion
    }
}